library(tidyverse)
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
── Attaching packages ─────────────────────────────────────────────────── tidyverse 1.3.2 ──✔ ggplot2 3.3.6      ✔ purrr   0.3.4 
✔ tibble  3.1.8      ✔ dplyr   1.0.10
✔ tidyr   1.2.1      ✔ stringr 1.4.1 
✔ readr   2.1.3      ✔ forcats 0.5.2 ── Conflicts ────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
library(lubridate)

Attaching package: ‘lubridate’

The following objects are masked from ‘package:base’:

    date, intersect, setdiff, union
load("data_ml.RData")

data_ml %>%
  filter(date > "1999-12-31",
         date < "2019-01-01") %>%
  arrange(stock_id, date)
NA
data_ml <- data_ml %>% 
  group_by(date) %>% 
  mutate(R1M_Usd_C = R1M_Usd > median(R1M_Usd),
         R12M_Usd_C = R12M_Usd > median(R12M_Usd)) %>%
  ungroup() %>% 
  mutate_if(is.logical, as.factor)

data_ml
stock_ids <- levels(as.factor(data_ml$stock_id))

stock_days <- data_ml %>% 
  group_by(stock_id) %>% 
  summarise(nb = n())

stock_ids_short <- stock_ids[which(stock_days$nb == max(stock_days$nb))]

returns <- data_ml %>% 
  filter(stock_id %in% stock_ids_short) %>% 
  dplyr::select(date, stock_id, R1M_Usd) %>% 
  pivot_wider(names_from = "stock_id",
              values_from = "R1M_Usd")
returns
NA

This dataset comprises information on 1,207 stocks listed in the US (possibly originating from Canada or Mexico). The time range starts in November 1998 and ends in March 2019. For each point in time, 93 characteristics describe the firms in the sample.

Our first observation is regarding the size factor, which states that companies with small capitalization tends grant higher returns than large capitalization. For that we create an equally weighted portofolio and summarize the mean return.

data_ml %>% 
  group_by(date) %>% 
  mutate(large = Mkt_Cap_12M_Usd > median(Mkt_Cap_12M_Usd)) %>%  # Creates a Large Cap colum
  ungroup() %>% 
  mutate(year = lubridate::year(date)) %>%                       # Creates a year variable
  group_by(year, large) %>% 
  summarize(avg_return = mean(R1M_Usd)) %>%                      # Compute the avg return
  
  #Plot
  
  ggplot(aes(x= year, y= avg_return, fill = large)) +
  geom_col(position = "dodge") + theme_light() +         
  theme(legend.position = c(0.8, 0.2)) +
  coord_fixed(124) + theme(legend.title = element_blank()) +
  scale_fill_manual(values = c("#F87E1F", "#0570EA"), name = "",
                    labels = c("Small", "Large"))
`summarise()` has grouped output by 'year'. You can override using the `.groups` argument.

library(quantmod) # Data extraction
Loading required package: xts
Loading required package: zoo

Attaching package: ‘zoo’

The following objects are masked from ‘package:base’:

    as.Date, as.Date.numeric


Attaching package: ‘xts’

The following objects are masked from ‘package:dplyr’:

    first, last

Loading required package: TTR
Registered S3 method overwritten by 'quantmod':
  method            from
  as.zoo.data.frame zoo 
library(xtable)   # LaTex exports

min_date <- "1963-07-31"
max_date <- "2020-03-28"

temp <- tempfile()

kf_website <- "http://mba.tuck.dartmouth.edu/pages/faculty/ken.french/"
kf_file <- "ftp/F-F_Research_Data_5_Factors_2x3_CSV.zip"

link <- paste0(kf_website, kf_file) # Linking the sites

download.file(link, temp, quiet = TRUE) #Download the site!

ff_factors <- read_csv(unz(temp, "F-F_Research_Data_5_Factors_2x3.csv"), skip = 2 ) %>% 
  rename(date = `...1`, MKT_RF = `Mkt-RF`) %>% 
  mutate_at(vars(-date), as.numeric) %>%                                  # Convert valeus to numbers
  mutate(date = ymd(parse_date_time(date, "%Y%m"))) %>%                   # Right format
  mutate(date = rollback(date + months(1))) %>%                           # End of month date
  mutate( MKT_RF = MKT_RF / 100,
              SMB = SMB / 100,
              HML = HML / 100,
              RMW = RMW / 100,
              CMA = CMA / 100,
              RF = RF/100) %>% 
  filter(date >= min_date, date <= max_date)  
New names:Warning: One or more parsing issues, call `problems()` on your data frame for details, e.g.:
  dat <- vroom(...)
  problems(dat)Rows: 775 Columns: 7── Column specification ───────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (7): ...1, Mkt-RF, SMB, HML, RMW, CMA, RF
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Warning: NAs introduced by coercionWarning: NAs introduced by coercionWarning: NAs introduced by coercionWarning: NAs introduced by coercionWarning: NAs introduced by coercionWarning: NAs introduced by coercionWarning:  60 failed to parse.
ff_factors
NA
ff_factors %>% 
  mutate(date = year(date)) %>% 
  gather(key= factor, value = value, -date) %>% 
  group_by(date, factor) %>% 
  summarise(value = mean(value)) %>% 
  ggplot(aes(x = date, y = value, color = factor)) +
  geom_line() + coord_fixed(500)
`summarise()` has grouped output by 'date'. You can override using the `.groups` argument.

nb_factors <- 5

data_fm <- left_join(data_ml %>% 
                      dplyr::select(date, stock_id, R1M_Usd) %>% 
                      filter(stock_id %in% stock_ids_short),
                    ff_factors,
                    by = "date") %>% 
  group_by(stock_id) %>% 
  mutate(R1M_Usd = lag(R1M_Usd)) %>% 
  ungroup() %>% 
  na.omit() %>% 
  pivot_wider(names_from = "stock_id", values_from = "R1M_Usd")

models <- lapply(paste0("`", stock_ids_short,
                        '` ~  MKT_RF + SMB + HML + RMW + CMA'),
                 function(f){lm(as.formula(f), data= data_fm,
                                na.action = "na.exclude") %>% 
                     summary() %>% 
                     "$"(coef) %>% 
                     data.frame() %>% 
                     dplyr::select(Estimate)}
                 )

betas <- matrix(unlist(models), ncol = nb_factors + 1, byrow = T) %>% 
  data.frame(row.names = stock_ids_short)
colnames(betas) <- c("Constant", "MKT_RF", "SMB", "HML", "RMW", "CMA")

knitr::kable(head(betas),  booktabs = TRUE,
             caption = "Sample of beta values (row numbers are stock IDs)") 
Sample of beta values (row numbers are stock IDs)
Constant MKT_RF SMB HML RMW CMA
3 -0.0017164 0.8020773 0.8356087 0.8320418 0.1128188 -0.2439281
4 0.0035508 0.3139552 0.2748537 -0.1524879 0.4576615 0.4708250
7 0.0049663 0.5261486 0.5214059 0.0306593 0.3206683 0.3530033
9 0.0044245 0.7470505 0.6267144 1.0142096 -0.0520735 -0.0600390
16 0.0011722 1.1883735 -0.1612352 1.3812378 0.1800120 -0.6041161
22 0.0018610 0.5919958 0.5707547 0.3307818 0.4701422 0.1969087

betas
loadings <- betas %>% 
  dplyr::select(-Constant) %>% 
  data.frame()

ret <- returns %>% 
  dplyr::select(-date) %>% 
  data.frame(row.names = returns$date) %>% 
  t()

fm_data <- cbind(loadings, ret)

fm_data
models <- lapply(paste("`", returns$date, "`", ' ~  MKT_RF + SMB + HML + RMW + CMA', sep = ""),
function(f){ lm(as.formula(f), data = fm_data) %>%                        # Call lm(.)
                         summary() %>%                                    # Gather the output
                         "$"(coef) %>%                                    # Keep only the coefs
                         data.frame() %>%                                 # Convert to dataframe
                         dplyr::select(Estimate)}                         # Keep only estimates
                 )
gammas <- matrix(unlist(models), ncol = nb_factors + 1, byrow = T) %>%    # Switch to dataframe
    data.frame(row.names = returns$date)                                  # & set row names
colnames(gammas) <- c("Constant", "MKT_RF", "SMB", "HML", "RMW", "CMA")   # Set col names

gammas

gammas[1:nrow(gammas),] %>%                                         # Take gammas:
    # The first row is omitted because the first row of returns is undefined
    dplyr::select(MKT_RF, SMB, HML) %>%                             # Select 3 factors
    bind_cols(date = data_fm$date) %>%          # Add date
    gather(key = factor, value = gamma,-date) %>% 
    ggplot(aes(x = date, y = gamma, color = factor)) +
    geom_line() + facet_grid(factor~.)

LS0tCnRpdGxlOiAiRmFjdG9yIEludmVzdGluZyB3aXRoIEFsdGVybmF0aXZlIERhdGEiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCmBgYHtyfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShsdWJyaWRhdGUpCgpsb2FkKCJkYXRhX21sLlJEYXRhIikKCmRhdGFfbWwgJT4lCiAgZmlsdGVyKGRhdGUgPiAiMTk5OS0xMi0zMSIsCiAgICAgICAgIGRhdGUgPCAiMjAxOS0wMS0wMSIpICU+JQogIGFycmFuZ2Uoc3RvY2tfaWQsIGRhdGUpCiAgICAgICAgCmBgYAoKCmBgYHtyfQpkYXRhX21sIDwtIGRhdGFfbWwgJT4lIAogIGdyb3VwX2J5KGRhdGUpICU+JSAKICBtdXRhdGUoUjFNX1VzZF9DID0gUjFNX1VzZCA+IG1lZGlhbihSMU1fVXNkKSwKICAgICAgICAgUjEyTV9Vc2RfQyA9IFIxMk1fVXNkID4gbWVkaWFuKFIxMk1fVXNkKSkgJT4lCiAgdW5ncm91cCgpICU+JSAKICBtdXRhdGVfaWYoaXMubG9naWNhbCwgYXMuZmFjdG9yKQoKZGF0YV9tbApgYGAKYGBge3J9CnN0b2NrX2lkcyA8LSBsZXZlbHMoYXMuZmFjdG9yKGRhdGFfbWwkc3RvY2tfaWQpKQoKc3RvY2tfZGF5cyA8LSBkYXRhX21sICU+JSAKICBncm91cF9ieShzdG9ja19pZCkgJT4lIAogIHN1bW1hcmlzZShuYiA9IG4oKSkKCnN0b2NrX2lkc19zaG9ydCA8LSBzdG9ja19pZHNbd2hpY2goc3RvY2tfZGF5cyRuYiA9PSBtYXgoc3RvY2tfZGF5cyRuYikpXQoKcmV0dXJucyA8LSBkYXRhX21sICU+JSAKICBmaWx0ZXIoc3RvY2tfaWQgJWluJSBzdG9ja19pZHNfc2hvcnQpICU+JSAKICBkcGx5cjo6c2VsZWN0KGRhdGUsIHN0b2NrX2lkLCBSMU1fVXNkKSAlPiUgCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9ICJzdG9ja19pZCIsCiAgICAgICAgICAgICAgdmFsdWVzX2Zyb20gPSAiUjFNX1VzZCIpCnJldHVybnMKCmBgYAoKClRoaXMgZGF0YXNldCBjb21wcmlzZXMgaW5mb3JtYXRpb24gb24gMSwyMDcgc3RvY2tzIGxpc3RlZCBpbiB0aGUgVVMgKHBvc3NpYmx5IG9yaWdpbmF0aW5nIGZyb20gQ2FuYWRhIG9yIE1leGljbykuIFRoZSB0aW1lIHJhbmdlIHN0YXJ0cyBpbiBOb3ZlbWJlciAxOTk4IGFuZCBlbmRzIGluIE1hcmNoIDIwMTkuIEZvciBlYWNoIHBvaW50IGluIHRpbWUsIDkzIGNoYXJhY3RlcmlzdGljcyBkZXNjcmliZSB0aGUgZmlybXMgaW4gdGhlIHNhbXBsZS4KCk91ciBmaXJzdCBvYnNlcnZhdGlvbiBpcyByZWdhcmRpbmcgdGhlIHNpemUgZmFjdG9yLCB3aGljaCBzdGF0ZXMgdGhhdCBjb21wYW5pZXMgd2l0aCBzbWFsbCBjYXBpdGFsaXphdGlvbiB0ZW5kcyBncmFudCBoaWdoZXIgcmV0dXJucyB0aGFuIGxhcmdlIGNhcGl0YWxpemF0aW9uLiBGb3IgdGhhdCB3ZSBjcmVhdGUgYW4gZXF1YWxseSB3ZWlnaHRlZCBwb3J0b2ZvbGlvIGFuZCBzdW1tYXJpemUgdGhlIG1lYW4gcmV0dXJuLgpgYGB7cn0KZGF0YV9tbCAlPiUgCiAgZ3JvdXBfYnkoZGF0ZSkgJT4lIAogIG11dGF0ZShsYXJnZSA9IE1rdF9DYXBfMTJNX1VzZCA+IG1lZGlhbihNa3RfQ2FwXzEyTV9Vc2QpKSAlPiUgICMgQ3JlYXRlcyBhIExhcmdlIENhcCBjb2x1bQogIHVuZ3JvdXAoKSAlPiUgCiAgbXV0YXRlKHllYXIgPSBsdWJyaWRhdGU6OnllYXIoZGF0ZSkpICU+JSAgICAgICAgICAgICAgICAgICAgICAgIyBDcmVhdGVzIGEgeWVhciB2YXJpYWJsZQogIGdyb3VwX2J5KHllYXIsIGxhcmdlKSAlPiUgCiAgc3VtbWFyaXplKGF2Z19yZXR1cm4gPSBtZWFuKFIxTV9Vc2QpKSAlPiUgICAgICAgICAgICAgICAgICAgICAgIyBDb21wdXRlIHRoZSBhdmcgcmV0dXJuCiAgCiAgI1Bsb3QKICAKICBnZ3Bsb3QoYWVzKHg9IHllYXIsIHk9IGF2Z19yZXR1cm4sIGZpbGwgPSBsYXJnZSkpICsKICBnZW9tX2NvbChwb3NpdGlvbiA9ICJkb2RnZSIpICsgdGhlbWVfbGlnaHQoKSArICAgICAgICAgCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gYygwLjgsIDAuMikpICsKICBjb29yZF9maXhlZCgxMjQpICsgdGhlbWUobGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiI0Y4N0UxRiIsICIjMDU3MEVBIiksIG5hbWUgPSAiIiwKICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKCJTbWFsbCIsICJMYXJnZSIpKQoKYGBgCgoKCmBgYHtyfQpsaWJyYXJ5KHF1YW50bW9kKSAjIERhdGEgZXh0cmFjdGlvbgpsaWJyYXJ5KHh0YWJsZSkgICAjIExhVGV4IGV4cG9ydHMKCm1pbl9kYXRlIDwtICIxOTYzLTA3LTMxIgptYXhfZGF0ZSA8LSAiMjAyMC0wMy0yOCIKCnRlbXAgPC0gdGVtcGZpbGUoKQoKa2Zfd2Vic2l0ZSA8LSAiaHR0cDovL21iYS50dWNrLmRhcnRtb3V0aC5lZHUvcGFnZXMvZmFjdWx0eS9rZW4uZnJlbmNoLyIKa2ZfZmlsZSA8LSAiZnRwL0YtRl9SZXNlYXJjaF9EYXRhXzVfRmFjdG9yc18yeDNfQ1NWLnppcCIKCmxpbmsgPC0gcGFzdGUwKGtmX3dlYnNpdGUsIGtmX2ZpbGUpICMgTGlua2luZyB0aGUgc2l0ZXMKCmRvd25sb2FkLmZpbGUobGluaywgdGVtcCwgcXVpZXQgPSBUUlVFKSAjRG93bmxvYWQgdGhlIHNpdGUhCgpmZl9mYWN0b3JzIDwtIHJlYWRfY3N2KHVueih0ZW1wLCAiRi1GX1Jlc2VhcmNoX0RhdGFfNV9GYWN0b3JzXzJ4My5jc3YiKSwgc2tpcCA9IDIgKSAlPiUgCiAgcmVuYW1lKGRhdGUgPSBgLi4uMWAsIE1LVF9SRiA9IGBNa3QtUkZgKSAlPiUgCiAgbXV0YXRlX2F0KHZhcnMoLWRhdGUpLCBhcy5udW1lcmljKSAlPiUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBDb252ZXJ0IHZhbGV1cyB0byBudW1iZXJzCiAgbXV0YXRlKGRhdGUgPSB5bWQocGFyc2VfZGF0ZV90aW1lKGRhdGUsICIlWSVtIikpKSAlPiUgICAgICAgICAgICAgICAgICAgIyBSaWdodCBmb3JtYXQKICBtdXRhdGUoZGF0ZSA9IHJvbGxiYWNrKGRhdGUgKyBtb250aHMoMSkpKSAlPiUgICAgICAgICAgICAgICAgICAgICAgICAgICAjIEVuZCBvZiBtb250aCBkYXRlCiAgbXV0YXRlKCBNS1RfUkYgPSBNS1RfUkYgLyAxMDAsCiAgICAgICAgICAgICAgU01CID0gU01CIC8gMTAwLAogICAgICAgICAgICAgIEhNTCA9IEhNTCAvIDEwMCwKICAgICAgICAgICAgICBSTVcgPSBSTVcgLyAxMDAsCiAgICAgICAgICAgICAgQ01BID0gQ01BIC8gMTAwLAogICAgICAgICAgICAgIFJGID0gUkYvMTAwKSAlPiUgCiAgZmlsdGVyKGRhdGUgPj0gbWluX2RhdGUsIGRhdGUgPD0gbWF4X2RhdGUpICAKCmZmX2ZhY3RvcnMKIApgYGAKCiAgCmBgYHtyfQpmZl9mYWN0b3JzICU+JSAKICBtdXRhdGUoZGF0ZSA9IHllYXIoZGF0ZSkpICU+JSAKICBnYXRoZXIoa2V5PSBmYWN0b3IsIHZhbHVlID0gdmFsdWUsIC1kYXRlKSAlPiUgCiAgZ3JvdXBfYnkoZGF0ZSwgZmFjdG9yKSAlPiUgCiAgc3VtbWFyaXNlKHZhbHVlID0gbWVhbih2YWx1ZSkpICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBkYXRlLCB5ID0gdmFsdWUsIGNvbG9yID0gZmFjdG9yKSkgKwogIGdlb21fbGluZSgpICsgY29vcmRfZml4ZWQoNTAwKQpgYGAKCmBgYHtyfQpuYl9mYWN0b3JzIDwtIDUKCmRhdGFfZm0gPC0gbGVmdF9qb2luKGRhdGFfbWwgJT4lIAogICAgICAgICAgICAgICAgICAgICAgZHBseXI6OnNlbGVjdChkYXRlLCBzdG9ja19pZCwgUjFNX1VzZCkgJT4lIAogICAgICAgICAgICAgICAgICAgICAgZmlsdGVyKHN0b2NrX2lkICVpbiUgc3RvY2tfaWRzX3Nob3J0KSwKICAgICAgICAgICAgICAgICAgICBmZl9mYWN0b3JzLAogICAgICAgICAgICAgICAgICAgIGJ5ID0gImRhdGUiKSAlPiUgCiAgZ3JvdXBfYnkoc3RvY2tfaWQpICU+JSAKICBtdXRhdGUoUjFNX1VzZCA9IGxhZyhSMU1fVXNkKSkgJT4lIAogIHVuZ3JvdXAoKSAlPiUgCiAgbmEub21pdCgpICU+JSAKICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gInN0b2NrX2lkIiwgdmFsdWVzX2Zyb20gPSAiUjFNX1VzZCIpCgptb2RlbHMgPC0gbGFwcGx5KHBhc3RlMCgiYCIsIHN0b2NrX2lkc19zaG9ydCwKICAgICAgICAgICAgICAgICAgICAgICAgJ2AgfiAgTUtUX1JGICsgU01CICsgSE1MICsgUk1XICsgQ01BJyksCiAgICAgICAgICAgICAgICAgZnVuY3Rpb24oZil7bG0oYXMuZm9ybXVsYShmKSwgZGF0YT0gZGF0YV9mbSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYS5hY3Rpb24gPSAibmEuZXhjbHVkZSIpICU+JSAKICAgICAgICAgICAgICAgICAgICAgc3VtbWFyeSgpICU+JSAKICAgICAgICAgICAgICAgICAgICAgIiQiKGNvZWYpICU+JSAKICAgICAgICAgICAgICAgICAgICAgZGF0YS5mcmFtZSgpICU+JSAKICAgICAgICAgICAgICAgICAgICAgZHBseXI6OnNlbGVjdChFc3RpbWF0ZSl9CiAgICAgICAgICAgICAgICAgKQoKYmV0YXMgPC0gbWF0cml4KHVubGlzdChtb2RlbHMpLCBuY29sID0gbmJfZmFjdG9ycyArIDEsIGJ5cm93ID0gVCkgJT4lIAogIGRhdGEuZnJhbWUocm93Lm5hbWVzID0gc3RvY2tfaWRzX3Nob3J0KQpjb2xuYW1lcyhiZXRhcykgPC0gYygiQ29uc3RhbnQiLCAiTUtUX1JGIiwgIlNNQiIsICJITUwiLCAiUk1XIiwgIkNNQSIpCgprbml0cjo6a2FibGUoaGVhZChiZXRhcyksICBib29rdGFicyA9IFRSVUUsCiAgICAgICAgICAgICBjYXB0aW9uID0gIlNhbXBsZSBvZiBiZXRhIHZhbHVlcyAocm93IG51bWJlcnMgYXJlIHN0b2NrIElEcykiKSAKCmJldGFzCmBgYAoKYGBge3J9CmxvYWRpbmdzIDwtIGJldGFzICU+JSAKICBkcGx5cjo6c2VsZWN0KC1Db25zdGFudCkgJT4lIAogIGRhdGEuZnJhbWUoKQoKcmV0IDwtIHJldHVybnMgJT4lIAogIGRwbHlyOjpzZWxlY3QoLWRhdGUpICU+JSAKICBkYXRhLmZyYW1lKHJvdy5uYW1lcyA9IHJldHVybnMkZGF0ZSkgJT4lIAogIHQoKQoKZm1fZGF0YSA8LSBjYmluZChsb2FkaW5ncywgcmV0KQoKZm1fZGF0YQpgYGAKYGBge3J9Cm1vZGVscyA8LSBsYXBwbHkocGFzdGUoImAiLCByZXR1cm5zJGRhdGUsICJgIiwgJyB+ICBNS1RfUkYgKyBTTUIgKyBITUwgKyBSTVcgKyBDTUEnLCBzZXAgPSAiIiksCmZ1bmN0aW9uKGYpeyBsbShhcy5mb3JtdWxhKGYpLCBkYXRhID0gZm1fZGF0YSkgJT4lICAgICAgICAgICAgICAgICAgICAgICAgIyBDYWxsIGxtKC4pCiAgICAgICAgICAgICAgICAgICAgICAgICBzdW1tYXJ5KCkgJT4lICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBHYXRoZXIgdGhlIG91dHB1dAogICAgICAgICAgICAgICAgICAgICAgICAgIiQiKGNvZWYpICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgS2VlcCBvbmx5IHRoZSBjb2VmcwogICAgICAgICAgICAgICAgICAgICAgICAgZGF0YS5mcmFtZSgpICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgQ29udmVydCB0byBkYXRhZnJhbWUKICAgICAgICAgICAgICAgICAgICAgICAgIGRwbHlyOjpzZWxlY3QoRXN0aW1hdGUpfSAgICAgICAgICAgICAgICAgICAgICAgICAjIEtlZXAgb25seSBlc3RpbWF0ZXMKICAgICAgICAgICAgICAgICApCmdhbW1hcyA8LSBtYXRyaXgodW5saXN0KG1vZGVscyksIG5jb2wgPSBuYl9mYWN0b3JzICsgMSwgYnlyb3cgPSBUKSAlPiUgICAgIyBTd2l0Y2ggdG8gZGF0YWZyYW1lCiAgICBkYXRhLmZyYW1lKHJvdy5uYW1lcyA9IHJldHVybnMkZGF0ZSkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyAmIHNldCByb3cgbmFtZXMKY29sbmFtZXMoZ2FtbWFzKSA8LSBjKCJDb25zdGFudCIsICJNS1RfUkYiLCAiU01CIiwgIkhNTCIsICJSTVciLCAiQ01BIikgICAjIFNldCBjb2wgbmFtZXMKCmdhbW1hcwpgYGAKCgpgYGB7cn0KCmdhbW1hc1sxOm5yb3coZ2FtbWFzKSxdICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBUYWtlIGdhbW1hczoKICAgICMgVGhlIGZpcnN0IHJvdyBpcyBvbWl0dGVkIGJlY2F1c2UgdGhlIGZpcnN0IHJvdyBvZiByZXR1cm5zIGlzIHVuZGVmaW5lZAogICAgZHBseXI6OnNlbGVjdChNS1RfUkYsIFNNQiwgSE1MKSAlPiUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgU2VsZWN0IDMgZmFjdG9ycwogICAgYmluZF9jb2xzKGRhdGUgPSBkYXRhX2ZtJGRhdGUpICU+JSAgICAgICAgICAjIEFkZCBkYXRlCiAgICBnYXRoZXIoa2V5ID0gZmFjdG9yLCB2YWx1ZSA9IGdhbW1hLC1kYXRlKSAlPiUgCiAgICBnZ3Bsb3QoYWVzKHggPSBkYXRlLCB5ID0gZ2FtbWEsIGNvbG9yID0gZmFjdG9yKSkgKwogICAgZ2VvbV9saW5lKCkgKyBmYWNldF9ncmlkKGZhY3Rvcn4uKQpgYGA=